﻿/*
   Copyright 2011 Softisis

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using FOM.Impl;
using FOM.Interfaces;

namespace FOM.Utils
{
    /// <summary>
    /// This static class contains come usefull method to manipulate pathes
    /// </summary>
    public static class PathHelper
    {
        /// <summary>
        /// Build a dot separated path from an expression. 
        /// It can support even inferred lambda as as lambda from instanciated.
        /// </summary>
        /// <param name="expr">The expression tree to analyze</param>
        /// <returns>The path. Empty means base object</returns>
        public static String BuildPathFromExpression(Expression expr)
        {
            string ret = "";

            Expression nextOne = expr;

            // Browse the expr tree
            bool fin = false; // will be set to true when the leaf is reached
            while (!fin)
            {
                if (nextOne == null)
                    break;

                switch (nextOne.NodeType)
                {
                    case ExpressionType.MemberAccess:
                        var mexpr = nextOne as MemberExpression;
                        if (mexpr == null)
                            throw new Exception("Invalid expression type (expected MemberAccess): " + nextOne);
                        ret = mexpr.Member.Name + "." + ret;
                        nextOne = mexpr.Expression;
                        break;

                    case ExpressionType.Parameter:
                        var pexpr = nextOne as ParameterExpression;
                        if (pexpr == null)
                            throw new Exception("Invalid expression type (expected Parameter): " + nextOne);
                        ret = pexpr.Name + "." + ret;
                        fin = true; // last element of tree. stop here
                        nextOne = null;
                        break;

                    // occuring when using an instance instead of inferred expression f.ex (in this case, type is Constant)
                    default:
                        fin = true; // over the path. Just stop
                        nextOne = null;
                        break;
                }

            }

            // remove the first element as it's not part of the relative members path, plus the last dot
            if (ret != null)
            {
                var pos = ret.IndexOf('.');
                var len = ret.Length - pos - 1;
                if (len > 0)
                    len--;
                ret = ret.Substring(pos + 1, len);

            }
            return ret;
        }


        /// <summary>
        /// Tests if a path is included in another path. Examples:
        /// Is foo.bar.y included in foo.bar ==> yes.
        /// Is foo.bar.hurg.z included in foo.bar ==> yes.
        /// Is foo included in foo.bar ==> no.
        /// Is foo.bar included in foo.bar. Well, depends on "strictinside" flag. If true, response is ==> "no".
        /// </summary>
        /// <param name="pathsource">Source path, like foo.bar.y</param>
        /// <param name="pathtotest">target path, like alpha.beta.x</param>
        /// <param name="strictinside">If true, the test path must be inside (so can't be equals to sourcepath)</param>
        /// <param name="strictunder">If true, the test path must be just under the ref path.
        /// Ex: Is foo.bar.y included in foo.bar ==> yes. Is foo.bar.hurg.x included in foo.bar ==> no.</param>
        /// <returns>True or false</returns>
        public static bool IsIncluded(String pathsource, String pathtotest, bool strictinside, bool strictunder)
        {
            // case path equality
            if (pathsource == pathtotest)
                return !strictinside;

            if (pathtotest == "") // if to test is not smthg ==> false
                return false;

            if (pathsource == "")
                return true;

            var tmpSource = pathsource.Split('.');
            var tmpTotest = pathtotest.Split('.');

            if (tmpSource.Length > tmpTotest.Length) // this means that this is an upper path
                return false;

            // finally, inclusion test
            for (int i = 0; i < tmpSource.Length; i++)
                if (tmpSource[i] != tmpTotest[i])
                    return false;

            // "just under" test: there must be one element remaining after
            if (strictunder && tmpSource.Length+1 != tmpTotest.Length)
                return false;

            return true;
        }


        /********* Protected area **************/

        /// <summary>
        /// This func builds a mapping from two lambda expressions
        /// </summary>
        /// <typeparam name="TProperty">Type of source target property</typeparam>
        /// <typeparam name="TProperty2">Type of target property</typeparam>
        /// <typeparam name="TSrc">Type of source object</typeparam>
        /// <typeparam name="TDst">Type of target object</typeparam>
        /// <param name="expsrc">Lambda expression to point at the source property</param>
        /// <param name="expdst">Lambda expression to point at the target property</param>
        /// <returns>Mapping</returns>
        public static MappingNodeImpl<TProperty, TProperty2> BuildMappingFromExpression<TSrc, TProperty, TDst, TProperty2>(
            Expression<Func<TSrc, TProperty>> expsrc,
            Expression<Func<TDst, TProperty2>> expdst
            )
        {
            var srcstr = PathHelper.BuildPathFromExpression(expsrc.Body);

            var dststr = srcstr;
            if (expdst != null)
                dststr = PathHelper.BuildPathFromExpression(expdst.Body);

            return new MappingNodeImpl<TProperty, TProperty2>(srcstr, dststr);
        }


        /// <summary>
        /// This func builds a Goto link from two lambda expressions
        /// </summary>
        /// <typeparam name="TProperty">Type of source target property</typeparam>
        /// <typeparam name="TProperty2">Type of target property</typeparam>
        /// <typeparam name="TSrc">Type of source object</typeparam>
        /// <typeparam name="TDst">Type of target object</typeparam>
        /// <param name="expsrc">Lambda expression to point at the source property</param>
        /// <param name="expdst">Lambda expression to point at the target property</param>
        /// <returns>Mapping</returns>
        public static GoToMappingNodeImpl<TProperty, TProperty2> BuildGotoLinkFromExpression<TSrc, TProperty, TDst, TProperty2>(
            Expression<Func<TSrc, TProperty>> expsrc,
            Expression<Func<TDst, TProperty2>> expdst
            )
        {
            var srcstr = PathHelper.BuildPathFromExpression(expsrc.Body);

            var dststr = srcstr;
            if (expdst != null)
                dststr = PathHelper.BuildPathFromExpression(expdst.Body);

            return new GoToMappingNodeImpl<TProperty, TProperty2>(srcstr, dststr);
        }

        /// <summary>
        /// Remove the empty pathes to have a canonical path
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static string ToCannonicalPath(string source)
        {
            if (source==null)
                return null;
            
            if (source == "")
                return "";

            var alls=source.Split('.');
            string str = null;
            foreach (var s in alls)
            {
                if (s == "") continue;

                if (str != null)
                    str += ".";
                str += s;
            }
            if (str == null)
                str = "";

            return str;
        }

    }
}
